#include "File.h"
#include "ObjMeshManager.h"
#include "DX11ErrorReport.h"
#include "ScreenManager.h"
#include "DX11ShaderManager.h"
#include "DX11VertexData.h"
#include "Timer.h"
#include "StringUtilities.h"
#include "LightingManager.h"
#include "MapFunctions.h"
#include "DX11TextureManager.h"

#include <memory>

ObjMesh::ObjMesh()
{
	m_enableNormalMapping = false;

	m_vertexBuffer = nullptr;
	m_indexBuffer = nullptr;

	ZeroMemory(&m_bd, sizeof(m_bd));
	ZeroMemory(&m_initData, sizeof(m_initData));

	//ZeroMemory(&*m_world, sizeof(*m_world));
	//m_world = new XMMATRIX;
	//XMMATRIX test = XMMatrixIdentity();

	D3DXMatrixIdentity(&m_world);

	m_numOfIndices = -1;

    D3DXVECTOR4 vLightDirs[2] =
    {
        D3DXVECTOR4( -0.577f, 0.577f, -0.577f, 1.0f ),	// changes light intensity O.o
        D3DXVECTOR4( 0.0f, 0.0f, -1.0f, 1.0f ), 
    };
    D3DXVECTOR4 vLightColors[2] = 
    {
        D3DXVECTOR4( 0.0f, 0.5f, 0.0f, 1.0f ),
        D3DXVECTOR4( 0.5, 0.0f, 1.0f, 1.0f )
    };
	// initialise default light
	// = no lighting
	// lighting manager stores default light and no light
	m_cb.light[0].direction = vLightDirs[0];
	m_cb.light[0].colour = vLightColors[0];
	m_cb.light[0].ambient = D3DXVECTOR4(0.7f, 0.7f, 0.7f, 1.0f);
	m_cb.m_outputColour = D3DXVECTOR4(.0f, .0f, .0f, .0f);
	m_cb.TEST = D3DXVECTOR4(1.0f, 0.0f, 0.0f, 1.0f);

	m_colour = D3DXVECTOR4(1.0f, 1.0f, 1.0f, 1.0f);
	m_usingSecondBuffer = false;
	//m_useLight = "DEFAULT";
}

ObjMesh::~ObjMesh()
{
	//delete m_vertexBuffer;
	//delete m_indexBuffer;

	if (m_vertexBuffer)
	{
		m_vertexBuffer->Release();
	}
	if (m_indexBuffer)
	{
		m_indexBuffer->Release();
	}
	if (m_pConstantBuffer)
	{
		m_pConstantBuffer->Release();
	}
}

//void* ObjMesh::operator new(size_t i)
//{
//	return _mm_malloc(i, 32);
//}
//
//void ObjMesh::operator delete(void* p)
//{
//	_mm_free(p);
//}

bool ObjMesh::Load(const std::string& meshName, const std::string& meshFileName, const std::wstring& shaderFile)
{
	m_meshName = meshName;
	int normalCounter = 0;

	File* file = new File;
	if (!file->OpenRead(meshFileName))
	{
		DX11ErrorMsg(L"Cannot open obj file", L"Obj Load Error", MB_OK);
		return false;
	}

	std::string lineContents;

	while (file->GetString(&lineContents))
	{
		Strings str = Split(lineContents, ' ');

		D3DXVECTOR4 white = D3DXVECTOR4(1, 1, 1, 1);
		D3DXVECTOR4 black = D3DXVECTOR4(0, 0, 0, 1);

		if (str[0] ==  "v")
		{
			m_vertData.push_back(VertexData());
			m_vertData.back().m_pos = D3DXVECTOR3(ToFloat(str[1]), ToFloat(str[2]), ToFloat(str[3]));
		}
		else if (str[0] == "f")
		{
			if (str.size() != 4)
			{
				DX11ErrorMsg(L"Did not triangulate mesh", L"Obj Mesh Error", MB_YESNO);
			}
			else
			{
				for (int i = 1; i < 4; i++)
				{
					Strings faceStr = Split(str[i], '/');
					// add assert
					m_indexData.push_back(ToInt(faceStr[0].c_str()) - 1);
					m_normalData.push_back(ToInt(faceStr[2].c_str()) - 1);
					m_uvData.push_back(ToInt(faceStr[1].c_str()) - 1);
				}
			}			
		}
		else if (str[0] == "vn")
		{
			m_normalIndices.push_back(D3DXVECTOR3(ToFloat(str[1]), ToFloat(str[2]), ToFloat(str[3])));

		}
		else if (str[0] == "vt")
		{
			m_uvIndices.push_back(D3DXVECTOR2(ToFloat(str[1]), ToFloat(str[2])));
		}
		else if (str[0] == "mtllib")
		{
			LoadMTL("../../Assets/" + str[1]);
		}
	}

	//for (int i = 0; i != m_vertData.size(); i++)
	//{
	//	m_vertData[i].m_normal = m_normalIndices[m_normalData[i]];
	//	m_vertData[i].m_texCoord = m_uvIndices[m_uvData[i]];
	//}

	std::vector<VertexData> FaceData;
	int numOfFaces = m_normalData.size();

	std::vector<DWORD> indexData;
	int counter = 0;
	for (DWORD j = 0; j < numOfFaces; j++)
	{
		FaceData.push_back(VertexData());

		FaceData.back().m_pos = m_vertData[m_indexData[j]].m_pos;
		FaceData.back().m_texCoord = m_uvIndices[m_uvData[j]];
		FaceData.back().m_normal = m_normalIndices[m_normalData[j]];

		indexData.push_back(j);
		//counter++;
	}

	float p = 0;

	HRESULT hr = S_OK;
	
	// if mtl file didn't have a texture - set texture to default
	if (m_textures.empty())
	{
		m_textures.insert(L"../../Assets/default_texture.png");
	}

	for (std::set<std::wstring>::iterator it = m_textures.begin(); it != m_textures.end(); ++it)
	{
		bool b = TheDX11TextureManager::Instance()->LoadTexture(*it);
		m_textureResource = TheDX11TextureManager::Instance()->UseTexture(*it);
		if (!b)
		{
			DX11ErrorMsg(L"failed to load texture", L"objMeshTexture Load error", MB_YESNO);
		}
	}

	hr = D3DX11CreateShaderResourceViewFromFile(TheScreenManager::Instance()->GetDevice(), L"../../Assets/normalmap.png",
												NULL, NULL, &m_normalMap, NULL);
	

	D3D11_SAMPLER_DESC sampDesc;
	ZeroMemory( &sampDesc, sizeof(sampDesc) );
	sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	//sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
	//sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
	//sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
	sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;;
	sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;;
    sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;;
    sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampDesc.MinLOD = 0;
    sampDesc.MaxLOD = D3D11_FLOAT32_MAX;

	hr = TheScreenManager::Instance()->GetDevice()->CreateSamplerState(&sampDesc, &m_textureSamplerState);
	if (FAILED(hr))
	{
		DX11ErrorMsg(L"failed to create texture sampler state", L"objMeshTexture Load error", MB_YESNO);
	}

	m_secondTextureResource = TheDX11TextureManager::Instance()->UseTexture(L"../../Assets/default_texture.png");

	// create shaders here
	TheDX11ShaderManager::Instance()->LoadShader(meshName + "_1", shaderFile, "PS", "VS"); // don't need this, load seperately
	TheDX11ShaderManager::Instance()->LoadShader(meshName + "_2", L"RenderToTarget.hlsl", "PS", "VS"); // don't need this, load seperately
	m_shaderName = meshName + "_1";
	// init buffers here
	TheScreenManager::Instance()->InitBuffers(m_vertexBuffer, m_indexBuffer, m_bd, m_initData, 
		FaceData, indexData, &m_pConstantBuffer);

	m_numOfIndices = indexData.size();

	// clear all obj data after it has been used here apart from m_indexData
	m_vertData.clear();
	m_uvData.clear();
	m_normalData.clear();
	m_uvIndices.clear();
	m_normalIndices.clear();
	FaceData.clear();
	indexData.clear();
	m_indexData.clear();
	file->CloseFile(meshFileName);

	return true;
}

void ObjMesh::Draw()
{
	// set lights
	//for (int i = 0; i != TheLightingManager::Instance()->GetNumberOfLights(); i++)
	//{
	//	Light l = TheLightingManager::Instance()->GetLight(i + 1);
	//	m_cb.light[i].ambient = l.ambient;
	//	m_cb.light[i].colour = l.colour;
	//	m_cb.light[i].direction = l.direction;
	//	m_cb.light[i].attenuation = l.attenuation;
	//	m_cb.light[i].range = l.range;
	//	m_cb.light[i].enabled = l.enabled;
	//	m_cb.light[i].position = l.position;
	//	m_cb.light[i].pad = l.pad;
	//}

	D3DXMatrixIdentity(&m_world);
	while (!m_transformationMatrices.empty())
	{
		D3DXMATRIX mat = m_transformationMatrices.top();
		m_world *= mat;
		m_transformationMatrices.pop();
	}
	m_cb.m_outputColour = m_colour;

	TheScreenManager::Instance()->SetBuffers(m_vertexBuffer, m_indexBuffer);

	D3DXMatrixTranspose(&m_cb.m_world, &m_world);
	D3DXMatrixTranspose(&m_cb.m_view, &TheScreenManager::Instance()->GetViewMatrix());
	D3DXMatrixTranspose(&m_cb.m_projection, &TheScreenManager::Instance()->GetProjectionMatrix());

	D3DXMatrixTranspose(&m_cb.m_lightViewMatrix, &TheLightingManager::Instance()->GetLightViewMatrix());
	D3DXMatrixTranspose(&m_cb.m_lightProjMatrix, &TheLightingManager::Instance()->GetLightProjMatrix());

	ID3D11Buffer* screenCb = TheScreenManager::Instance()->GetConstantBuffer();
	
	TheScreenManager::Instance()->GetDeviceContext()->UpdateSubresource(m_pConstantBuffer, 0, NULL, &m_cb, 0, 0);

	TheScreenManager::Instance()->GetDeviceContext()->VSSetShader(
		TheDX11ShaderManager::Instance()->UseShader(m_shaderName).first, NULL, 0);
	TheScreenManager::Instance()->GetDeviceContext()->PSSetShader(
		TheDX11ShaderManager::Instance()->UseShader(m_shaderName).second, NULL, 0);

	TheScreenManager::Instance()->GetDeviceContext()->VSSetConstantBuffers(0, 1, &m_pConstantBuffer);
	
	TheScreenManager::Instance()->GetDeviceContext()->PSSetConstantBuffers(0, 1, &m_pConstantBuffer);

	TheScreenManager::Instance()->GetDeviceContext()->PSSetShaderResources(0, 1, &m_textureResource);
	TheScreenManager::Instance()->GetDeviceContext()->PSSetShaderResources(1, 1, &m_secondTextureResource);

	TheScreenManager::Instance()->GetDeviceContext()->PSSetSamplers(0, 1, &m_textureSamplerState);
	

	TheScreenManager::Instance()->GetDeviceContext()->DrawIndexed(m_numOfIndices, 0, 0);
}

void ObjMesh::LoadMTL(const std::string& filename)
{
	File* file = new File;
	if (!file->OpenRead(filename))
	{
		DX11ErrorMsg(L"could not find mtl file", L"obj mesh load error", MB_YESNO);
	}

	std::string lineContents;
	while(file->GetString(&lineContents))
	{
		Strings str = Split(lineContents, ' ');

		if (str[0] == "map_Kd")
		{
			str[1] = "../../Assets/" + str[1];
			m_textures.insert(std::wstring(str[1].begin(), str[1].end()));
		}
	}
}

void ObjMesh::AddTexture(const std::wstring& texturePath)
{
	m_textures.insert(texturePath);
	bool b = TheDX11TextureManager::Instance()->LoadTexture(texturePath);
	if (!b)
	{
		DX11ErrorMsg(L"failed to load texture", L"objMeshTexture Load error", MB_YESNO);
	}
}

void ObjMesh::RemoveTexture(const std::wstring& texturePath)
{
	std::set<std::wstring>::iterator it = m_textures.find(texturePath);
	if (it == m_textures.end())
	{
		// report error
	}

	m_textures.erase(it);
}

void ObjMesh::EnableNormalMapping()
{
	m_enableNormalMapping = true;
}

void ObjMesh::DisableNormalMapping()
{
	m_enableNormalMapping = false;
}

void ObjMesh::SetColour(const D3DXVECTOR4& colour)
{
	m_colour = colour;
}

D3DXMATRIX& ObjMesh::GetMeshWorld()
{
	return m_world;
}

void ObjMesh::Rotate(float angle, float x, float y, float z)
{
	D3DXMATRIX rotation;
	D3DXMATRIX tempMat;
	D3DXMatrixIdentity(&rotation);

	D3DXMatrixRotationX(&tempMat, angle * x);
	rotation *= tempMat;
	D3DXMatrixRotationY(&tempMat, angle * y);
	rotation *= tempMat;
	D3DXMatrixRotationZ(&tempMat, angle * z);
	rotation *= tempMat;

	m_transformationMatrices.push(rotation);
}

void ObjMesh::Translate(float x, float y, float z)
{
	D3DXMATRIX translation;
	D3DXMatrixTranslation(&translation, x, y, z);

	m_transformationMatrices.push(translation);
}

void ObjMesh::Translate(const D3DXVECTOR3& pos)
{
	D3DXMATRIX translation;
	D3DXMatrixTranslation(&translation, pos.x, pos.y, pos.z);

	m_transformationMatrices.push(translation);
}

void ObjMesh::Scale(float x, float y, float z)
{
	D3DXMATRIX scale;
	D3DXMatrixScaling(&scale, x, y, z);

	m_transformationMatrices.push(scale);
}

void ObjMesh::Scale(const D3DXVECTOR3& scaling)
{
	D3DXMATRIX scale;
	D3DXMatrixScaling(&scale, scaling.x, scaling.y, scaling.z);

	m_transformationMatrices.push(scale);
}

void ObjMesh::Transform(const D3DXMATRIX& mat)
{
	m_transformationMatrices.push(mat);
}

void ObjMesh::SetWorldMatrix(const D3DXMATRIX& world)
{
	m_world = world;
}

void ObjMesh::ChangeTexture(ID3D11ShaderResourceView* texture)
{
	m_usingSecondBuffer = true;
	m_textureResource = texture;
}

void ObjMesh::ChangeTexture2(ID3D11ShaderResourceView* texture)
{
	m_usingSecondBuffer = true;
	m_secondTextureResource = texture;
}

void ObjMesh::ChangeShader(const std::string& shaderName)
{
	m_shaderName = shaderName;
}

void ObjMesh::SetTextureTranslation(const D3DXVECTOR2& uv)
{
	m_cb.m_texturePosition = uv;
}

//void ObjMesh::UseLight(const std::string& lightTag)
//{
//	m_useLight = lightTag;
//}

ObjMeshManager::ObjMeshManager()
{
}

ObjMeshManager::~ObjMeshManager()
{
	for (std::map<std::string, ObjMesh*>::iterator it = m_meshes.begin(); it != m_meshes.end(); ++it)
	{
		delete m_meshes[it->first];
	}
	m_meshes.clear();
}

bool ObjMeshManager::LoadObj(const std::string& fileName, const std::string& meshName, const std::wstring& shaderFile)
{
	m_meshes[meshName] = new ObjMesh;
	if (!m_meshes[meshName]->Load(meshName, fileName, shaderFile))
	{
		delete m_meshes[meshName];
		return false;
	}
	return true;
}

void ObjMeshManager::Draw(const std::string& meshName)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->Draw();
}

//bool ObjMeshManager::ReleaseVertBuffer()
//{
//    if(m_pVertexBuffer) m_pVertexBuffer->Release();
//	if(m_pVertexLayout)	m_pVertexLayout->Release();
//
//	return true;
//}

D3DXMATRIX& ObjMeshManager::GetMeshWorld(const std::string& meshName)
{
	CheckMapElement(m_meshes, meshName);
	return m_meshes[meshName]->GetMeshWorld();
}

void ObjMeshManager::SetWorldMatrix(const std::string& meshName, D3DXMATRIX& world)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->SetWorldMatrix(world);
}

void ObjMeshManager::Rotate(const std::string& meshName, float angle, float x, float y, float z)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->Rotate(angle, x , y, z);
}

void ObjMeshManager::Translate(const std::string& meshName, float x, float y, float z)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->Translate(x, y, z);
}

void ObjMeshManager::Scale(const std::string& meshName, const D3DXVECTOR3& scale)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->Scale(scale);
}

void ObjMeshManager::Scale(const std::string& meshName, float x, float y, float z)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->Scale(x, y, z);
}

//void ObjMeshManager::UseLight(const std::string& meshName, const std::string& lightTag)
//{
//	CheckMapElement(m_meshes, meshName);
//	m_meshes[meshName]->UseLight(lightTag);
//}

void ObjMeshManager::AddTexture(const std::string& meshName, const std::wstring& texturePath)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->AddTexture(texturePath);
}

void ObjMeshManager::RemoveTexture(const std::string& meshName, const std::wstring& texturePath)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->RemoveTexture(texturePath);
}

void ObjMeshManager::EnableNormalMapping(const std::string& meshName)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->EnableNormalMapping();
}

void ObjMeshManager::DisableNormalMapping(const std::string& meshName)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->DisableNormalMapping();
}

void ObjMeshManager::SetColour(const std::string& meshName, const D3DXVECTOR4& colour)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->SetColour(colour);
}

void ObjMeshManager::ChangeTexture(const std::string& meshName, ID3D11ShaderResourceView* texture)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->ChangeTexture(texture);
}

void ObjMeshManager::ChangeTexture2(const std::string& meshName, ID3D11ShaderResourceView* texture)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->ChangeTexture2(texture);
}

void ObjMeshManager::ChangeShader(const std::string& meshName, const std::string& shaderName)
{
	CheckMapElement(m_meshes, meshName);
	if (TheDX11ShaderManager::Instance()->GetVertShader(shaderName) == nullptr)
	{
		std::wstring msg = L"Did not find shader " + std::wstring(shaderName.begin(), shaderName.end());
		DX11ErrorMsg(msg.c_str(), L"ObjMeshManager::ChangeShader()", MB_OK);
	}
	m_meshes[meshName]->ChangeShader(shaderName);
}

void ObjMeshManager::Translate(const std::string& meshName, const D3DXVECTOR3& pos)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->Translate(pos);
}

void ObjMeshManager::Transform(const std::string& meshName, const D3DXMATRIX& mat)
{
	CheckMapElement(m_meshes, meshName);
	m_meshes[meshName]->Transform(mat);
}